package com.blindBox.front.token;

import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Calendar;

@Component
public class TokenTool {
    private static final String defaultMap =
            "8qhWL5KuEPdplG3bf2c-ge0HN6UwiYItvBa~r7onMyZQsJCVkOT9F4zS1RmDXAjx";
    private final char[] map;
    private final int[] idx = new int[26];
    private final int[] next = new int[65];

    /**
     * 初始化哈希表
     * jdk的HashMap过于庞大
     * 用简易hash函数 + 链式前向星代替
     * 实现 char -> 16进制值
     */
    private void initHashMap(String key) {
        Arrays.fill(next, -1);
        Arrays.fill(idx, -1);
        for (int i = 0; i < key.length(); ++i) {
            int k = key.charAt(i) % 26;
            if (idx[k] == -1) idx[k] = i;
            else {
                int s = idx[k];
                while (next[s] != -1) s = next[s];
                next[s] = i;
            }
        }
    }

    private String randomKey(){
        char[] chars = TokenTool.defaultMap.toCharArray();
        for (int i = 63; i >= 0; --i) {
            int b = (int) (Math.random() * (i + 1));
            char t = chars[b];
            chars[b] = chars[i];
            chars[i] = t;
        }
        return new String(chars);
    }

    /**
     * 返回传入的char所对应64进制值
     */
    private int getVal(char c) {
        int k = idx[c % 26];
        while (map[k] != c) k = next[k];
        return k;
    }

    public TokenTool() {
        //String randomKey = randomKey();
       String randomKey = defaultMap;
        this.map = randomKey.toCharArray();
        this.initHashMap(randomKey);
    }

    private int PCut(int i) {
        return i % 7;
    }

    /**
     * 返回 PCut 的镜像值
     */
    private int ECut(int i) {
        return (3 - (i >> 2 << 2 ^ i) + (i / 4 << 2)) % 7;
    }


    /**
     * 生成一个长度位7的token前缀
     * 前缀包含过期时间
     */
    public String setTimeLimit(long halfHourNum) {
        char[] str = new char[7];
        int l = (int) ((System.currentTimeMillis() + 1800000 * halfHourNum) / 60000);
        int A = 0, B = 0;
        while (A == B) {
            A = (int) (Math.random() * 64);
            B = (int) (Math.random() * 64);
        }
        str[0] = map[A];
        str[1] = map[B];
        int VA = A << 24 | (B << 12) | A;
        int VB = B << 24 | (A << 12) | B;
        int k = l ^ VA ^ VB;
        for (int i = 2; i < 7; ++i) {
            str[i] = map[k >> 6 << 6 ^ k];
            k >>= 6;
        }
        return new String(str);
    }

    /**
     * 加密字符串 返回加密后的内容
     */
    public String encipher(String content, long halfHourNum) {
        String key = setTimeLimit(halfHourNum);
        int[] keyV = new int[7];
        for (int i = 0; i < 7; ++i) keyV[i] = getVal(key.charAt(i));
        byte[] b = content.getBytes(StandardCharsets.UTF_8);
        int left = (b.length % 3 == 0) ? 0 : (b.length % 3 == 1 ? 2 : 3);
        char[] val = new char[b.length / 3 * 4 + left];
        int cut = 0;
        for (int i = 2; i < b.length; i += 3) {
            int t = 0;
            for (int j = 0; j < 3; ++j) {
                t <<= 8;
                t |= (b[i - j] + 128);
            }
            int star = ((i + 1) / 3 - 1) << 2;
            for (int j = 0; j < 4; ++j) {
                val[star + j] = map[(t >> 6 << 6 ^ t) ^ keyV[ECut(cut++)]];
                t >>= 6;
            }
        }
        if (left == 2) {
            int t = (b[b.length - 1] + 128);
            val[val.length - 2] = map[(t >> 6 << 6 ^ t) ^ keyV[0]];
            t >>= 6;
            val[val.length - 1] = map[t ^ keyV[1]];
        }
        if (left == 3) {
            int t = (b[b.length - 1] + 128);
            t <<= 8;
            t |= (b[b.length - 2] + 128);
            for (int i = 3; i > 0; --i) {
                val[val.length - i] = map[(t >> 6 << 6 ^ t) ^ keyV[i]];
                t >>= 6;
            }
        }
        return key + new String(val);
    }

    /**
     * 传入加密后的字符串
     * 解密原内容
     */
    public String parse(String str) {
        try {
            int len = str.length();
            int[] keyV = new int[7];
            for (int i = 0; i < 7; ++i) keyV[i] = getVal(str.charAt(i));
            int left = (len - 7) % 4;
            byte[] val = new byte[((len - 7) >> 2) * 3 + (left == 0 ? 0 : (left == 2 ? 1 : 2))];
            int cut = 0;
            for (int i = 10; i < len; i += 4) {
                int t = 0;
                for (int j = 0; j < 4; ++j) {
                    t <<= 6;
                    t |= getVal(str.charAt(i - j)) ^ keyV[PCut(cut++)];
                }
                int start = ((i - 6) / 4 - 1) * 3;
                for (int j = 0; j < 3; ++j) {
                    val[start + j] = (byte) (-128 + (t >> 8 << 8 ^ t));
                    t >>= 8;
                }
            }
            int t = 0;
            if (left == 2) {
                t |= getVal(str.charAt(len - 1)) ^ keyV[1];
                t <<= 6;
                t |= getVal(str.charAt(len - 2)) ^ keyV[0];
                val[val.length - 1] = (byte) (-128 + t);
            }
            if (left == 3) {
                for (int i = 1; i <= 3; ++i) {
                    t <<= 6;
                    t |= getVal(str.charAt(len - i)) ^ keyV[i];
                }
                val[val.length - 2] = (byte) (-128 + (t >> 8 << 8 ^ t));
                t >>= 8;
                val[val.length - 1] = (byte) (-128 + t);
            }
            return new String(val);
        } catch (Exception e) {
            System.out.println("==============================================");
            System.out.println("token中出现异常字符,请检查token或重新启动");
            System.out.println(str);
            System.out.println(e.toString());
            System.out.println("==============================================");
            return null;
        }
    }

    public Calendar getExpiredTime(String str) {
        int l = 0;
        for (int i = 6; i > 1; --i) {
            l <<= 6;
            l |= getVal(str.charAt(i));
        }
        int A = getVal(str.charAt(0));
        int B = getVal(str.charAt(1));
        int VA = A << 24 | (B << 12) | A;
        int VB = B << 24 | (A << 12) | B;
        int k = l ^ VA ^ VB;
        Calendar c = Calendar.getInstance();
        c.setTimeInMillis(k * 60000L);
        return c;
    }
}
